/**
 * Title:       Agent.java
 * Copyright:   Copyright (c) 1996-2004 The Agent Factory Working Group. All rights reserved.
 * Licence:     This file is free software; you can redistribute it and/or modify
 *              it under the terms of the GNU Lesser General Public License as published by
 *              the Free Software Foundation; either version 2.1, or (at your option)
 *              any later version.
 *
 *              This file is distributed in the hope that it will be useful,
 *              but WITHOUT ANY WARRANTY; without even the implied warranty of
 *              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *              GNU Lesser General Public License for more details.
 *
 *              You should have received a copy of the GNU Lesser General Public License
 *              along with Agent Factory; see the file COPYING.  If not, write to
 *              the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *              Boston, MA 02111-1307, USA.
 */
package com.agentfactory.platform.core;

import java.util.*;
import com.agentfactory.platform.AgentPlatform;
import com.agentfactory.platform.core.AgentConfigurationException;
import com.agentfactory.platform.util.Logger;
import com.agentfactory.platform.mts.MessageTransportService;
import com.agentfactory.platform.mts.FIPAMessageQueue;
import com.agentfactory.platform.scheduler.Scheduler;
import com.agentfactory.platform.service.PlatformService;
import com.agentfactory.platform.service.PlatformServiceManager;

/**
 * This class represents the embodiment of an agent within the AgentFactory
 * runtime environment. Memebers include the agents name and various objects
 * used by the AF platform's services.
 *
 * This class is abstract and must be instantiated for a given agent oriented
 * programming language. A number of abstract methods are defined, each of these
 * will have to be made concrete to provide the logical elements of a successful
 * agent implementation.
 *
 * There is an arguement that some of the meta information contained here could
 * be contained in the agent's own mental state. For example, the agent's
 * beliefs could easily include information about the agent's name and required
 * services. Rather than holding that information in the mental state, we hold it
 * here. We do this because: (1) it cuts down on the information that needs to be
 * reasoned about by the agent; (2) it removes the need to enforece the use of
 * specific self knowledge actuators; (3) since this information is meta
 * information about the state of the agent implementation, it is subalbe to
 * store the information out of the agent's mental state.
 *
 * TODO - robertr - 13-Aug-2004 - Code review on migration.
 *
 * @author Robert Ross
 */
public abstract class Agent extends Observable implements Observer {
    
    /**
     * FIPA INITIATED STATE
     */
    public static final int INITIATED                   = 0;
    
    /**
     * FIPA ACTIVE STATE
     */
    public static final int ACTIVE                      = 1;
    
    /**
     * FIPA WAITING STATE
     */
    public static final int WAITING                     = 2;
    
    /**
     * FIPA SUSPENDED STATE
     */
    public static final int SUSPENDED                   = 3;

    /**
     * FIPA TRANSIT STATE
     */
    public static final int TRANSIT                     = 4;

    /**
     * FIPA TERMINATED STATE
     */
    public static final int TERMINATED                  = 5;
    
    public static final int TERMINATING                 = 6;
    /**
     * The class's logging level.
     */
    private static final int CLASS_LOG_LEVEL = Logger.LEVEL_ERROR;
    
    /**
     * The default time slice is 200 milliseconds
     */
    private static final int DEFAULT_TIME_SLICE         = 200;
    
    /**
     * Indicate the state of the agent. The state can only be in one of the
     * states explicitly defined above.
     */
    protected int state;
    
    /**
     * The amount of time that the agent has for execution...
     */
    protected int timeSlice                             = DEFAULT_TIME_SLICE;
    
    /**
     * The agent's name.
     */
    public String name;
    
    /**
     * A Queue of FIPA compliant messages.
     */
    protected FIPAMessageQueue fipaMessageQueue;
    
    /**
     * The AF Platform on which the agent is running.
     */
    public AgentPlatform agentPlatform;
    
    /**
     * The Agent ID of this agent.
     */
    private AgentID agentID;
    
    /**
     * A container of all agent IDs known by the agent.
     */
    private HashMap agentIDs;
    
    /**
     * The list of services used by the agent.
     */
    private Hashtable services;
    private ArrayList serviceList;
    
    /**
     * The arguements supplied to the agent interpreter
     */
    protected Vector args;
    
    
    /**
     * The main constructor for the Agent class. Agent Classes should subclass
     * this method, calling this super constructor and initialising their own
     * state.
     * @param inName - A String object that hods the given name of an agent
     * @param inType - A String object that holds the type of the agent
     * @param inAgentPlatform - An AgentPlatform objet that is running this
     * agent interpreter.
     * @param inArguements - A Vector object containing any arguements supplied
     * to the interpreter. These will have been defined in the platform
     * configuration file and are all String objects.
     * @throws AgentConfigurationException - Thrown if there were any problems
     * instantiating the agent instance.
     */
    public Agent(String name, String inType,
            AgentPlatform platform,
            Vector arguments) throws AgentConfigurationException {
        
        agentPlatform = platform;
        this.name = name;
        args = arguments;
        
        // Construct core data structures for use by agent
        fipaMessageQueue = new FIPAMessageQueue(this);
        agentIDs = new HashMap();
        services = new Hashtable();
        serviceList = new ArrayList();
    }
    
    /**
     * Perform meta registration to activate FIPA type services. This method is
     * called by the agent platform following construction of the agent object.
     */
    public void metaRegistration() {
        Logger.enter("Agent:metaRegistration("+this.name+","+this.getType()+")", CLASS_LOG_LEVEL);
        
        // BIND TO AVAILABLE MTS:
        ArrayList mts = agentPlatform.getPlatformServiceManager().getAvailableMTS(name);
        for (int i=0; i<mts.size(); i++) {
            bindToService((String) mts.get(i));
        }
        
        createAgentID();
        
        Scheduler.getInstance().registerAgent(this);
        Logger.exit("Agent:metaRegistration()", CLASS_LOG_LEVEL);
    }
    
    /**
     * Set the state of the agent interperter.
     *
     * @param state - The state to which the agent is to be set.
     */
    public void setState(int state) {
        this.state = state;
    }
    
    /**
     * Get the State of the Agent Interperter.
     *
     * @return one of the valid fipa states (specified as constants above)
     */
    public int getState() {
        return state;
    }
    
    /**
     * Get the agent's message queuee
     *
     * @return FIPAMessageQueue - the agent's unique message queue.
     */
    public FIPAMessageQueue getMessageQueue() {
        return fipaMessageQueue;
    }
    
    
    /***************************************************************************
     * Methods concerned with the platform services used by the agent.
     **************************************************************************/
    /**
     * this method attempts to bind the agent to a specified service.  The
     * return value indicates the success or failure of the binding attempt.
     *
     * @param serviceID the id of the service that the agent is to attempt to bind to
     * @return true if the agent binds to the service, false otherwise#
     */
    public boolean bindToService(String serviceID) {
        PlatformServiceManager manager = agentPlatform.getPlatformServiceManager();
        PlatformService service = manager.bindToService(serviceID, this);
        
        if (service == null) {
            return false;
        }
        
        int i = 0;
        services.put(serviceID, service);
        int priority = service.getPriority();
        while((i<serviceList.size()) && ((PlatformService) serviceList.get(i)).getPriority() >= priority)
             i++;
        
        serviceList.add(i,service);
        
        // If the service is a MTS, then we need to update the AgentID
        if (service instanceof MessageTransportService) {
            createAgentID();
        }
        
        return true;
    }
    
    /**
     * Return the services available.
     *
     * @return Vector the services that the agent needs and are available on the current platform.
     */
    public Vector getAvailableMTSServices() {
        Vector mtsServices = new Vector();
        if (serviceList == null) {
            return mtsServices;
        }
        
        PlatformService service = null;
        for (int i=0; i < serviceList.size(); i++) {
            service = (PlatformService) serviceList.get(i);
            if (service instanceof MessageTransportService) {
                mtsServices.addElement(service);
            }
        }
        
        return mtsServices;
    }
    
    /**
     * Get the services that the agent is acutally bound to on this platform.
     *
     * @return Enumeration - the services that the agent needs and are available
     *         on the current platform.
     */
    public Enumeration getBoundServices() {
        return services.elements();
    }
    
    public PlatformService getService(String name) {
        return (PlatformService) services.get(name);
    }
    
    /***************************************************************************
     * Methods concerned with the Agent's Addressbook.
     ***************************************************************************
     *
     * This method allows the addition of agent-identifiers to the agents'
     * internal address list.
     *
     * @param id an AgentID
     */
    public void addAgentID(AgentID id) {
        agentIDs.put(id.getName(), id);
    }
    
    /**
     * Creates a unique FIPA Agent ID for the agent.
     */
    private void createAgentID() {
        ArrayList addresses = new ArrayList();
        
        Vector mts = getAvailableMTSServices();
        MessageTransportService service = null;
        for (int i = 0; i < mts.size(); i++) {
            service = (MessageTransportService) mts.elementAt(i);
            addresses.add(service.getAddress());
        }
        
        agentID = new AgentID(name, addresses, new ArrayList());
    }
    
    /**
     * Retrives the AgentID associated with the given name.
     *
     * @param inName the name of the agent whose ID is required.
     * @return an AgentID
     */
    public AgentID getAgentID(String inName) {
        return (AgentID) agentIDs.get(inName);
    }
    
    /**
     * Remove an agent ID from the agent's addressbook
     *
     * @param inName The name of the agent that is to be removed from the addressbook.
     */
    public void removeAgentID(String inName) {
        agentIDs.remove(inName);
    }
    
    /**
     * Change the AID Name.
     * @param inName
     */
    public void changeAIDName(String inName) {
        String oldName = agentID.getName();
        this.name = inName;
        
        // Create a new AgentID for the agent
        createAgentID();
        
        Enumeration e = services.elements();
        PlatformService service = null;
        while (e.hasMoreElements()) {
            service = (PlatformService) e.nextElement();
            service.modifyBinding(oldName, this.name);
        }
    }
    
    public AgentID getAgentID() {
        return this.agentID;
    }
    
    public Collection getAgentIDs() {
        return this.agentIDs.values();
    }
    
    /**
     * Get the agent's name.
     *
     * @return String - The agent's name.
     */
    public String getName() {
        return name;
    }
    
    /**
     * Get a handle to the AgentPlatform on which this agent is running
     *
     * @return AgentPlatform
     */
    public AgentPlatform getAgentPlatform() {
        return agentPlatform;
    }
    
    /**
     * Get the current time slice
     */
    public int getTimeSlice() {
        return timeSlice;
    }
    
    /**
     * Set the current time slice
     *
     * @param timeSlice the new time slice value
     */
    public void setTimeSlice( int timeSlice ) {
        this.timeSlice = timeSlice;
    }
    
    public void suspend() {
        state = Agent.SUSPENDED;
    }
    
    public void resume() {
        state = Agent.ACTIVE;
    }
    
    public String toString() {
        return name;
    }
    
    /**
     *
     */
    public void update(Observable o, Object arg) {
        Logger.enter("Agent:update("+ (String) arg + ")", CLASS_LOG_LEVEL);
        StringTokenizer tok = new StringTokenizer((String) arg, " ");
        
        String command = tok.nextToken();
        if (command.equals("UnbindService")) {
            this.services.remove(tok.nextToken());
        }
    }
    
    /***************************************************************************
     * OBSERVER METHODS
     ***************************************************************************/
    public void notify(String message) {
        setChanged();
        notifyObservers(message);
    }
    
    /* **************************************************************
     * Abstract Method Definitions
     * Each of these methods are available for use for the Agent Platform
     * to manipulate the agent.
     * **************************************************************/
    public abstract String getType();
    
    public abstract void step();
    
    public abstract void terminate();
    
    public abstract void endOfTimeSlice();
    
    public abstract void execute();
    
    /**
     * Called directly by the ScriptHandler class, this method is used to
     * initialized a given type of agent.
     */
    public abstract void initialise(String content);
}
